using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;

using Pegasus.Reflection;
using Pegasus.Runtime.Serialization;
using Pegasus.Runtime.Serialization.Formatters;
using Pegasus.Log4Net;

namespace Pegasus.Runtime.Serialization.Formatters.Xml
{
    /// <summary>
    /// 
    /// </summary>
	public class XmlFormatter2 : IFormatter
	{
        private static ILog s_logger = LogManager.GetLogger( typeof( XmlFormatter2 ) );

		// Local Instance Values
		private ISurrogateSelector m_surrogateSelector = null;
		private StreamingContext m_streamingContext = new StreamingContext( StreamingContextStates.All );
		private SerializationBinder m_binder = new ObjectSerializationBinder();
		private XmlWriterSettings m_xmlSettings = new XmlWriterSettings();

		private FormatterObjectManager m_objectManager = new FormatterObjectManager();
		private FormatterTypeManager m_typeManager = new FormatterTypeManager();

		private XmlWriter m_xmlWriter = null;

		private XmlDocument m_xmlDoc = null;

        private IFormatterConverter m_deserializationTypeConverter = null;
        
		// Local Const Values
		private readonly Version CurrentVersion = new Version( 2, 0 );

		/// <summary>
		/// Initializes a new instance of the <see cref="T:XmlFormatter2"/> class.
		/// </summary>
		public XmlFormatter2()
		{
			// Set the default XML Settings
			m_xmlSettings.ConformanceLevel = ConformanceLevel.Document;
            m_xmlSettings.Encoding = new UTF8Encoding( false );
			m_xmlSettings.Indent = true;
			m_xmlSettings.OmitXmlDeclaration = true;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:XmlFormatter2"/> class.
		/// </summary>
		/// <param name="surrogateSelector">The surrogate selector.</param>
		/// <param name="streamingContext">The streaming context.</param>
		public XmlFormatter2( ISurrogateSelector surrogateSelector, StreamingContext streamingContext ) :
			this()
		{
			m_surrogateSelector = surrogateSelector;
			m_streamingContext = streamingContext;
		}

		/// <summary>
		/// When overridden in a derived class, gets or sets the <see cref="T:System.Runtime.Serialization.SerializationBinder"></see> used with the current formatter.
		/// </summary>
		/// <value></value>
		/// <returns>The <see cref="T:System.Runtime.Serialization.SerializationBinder"></see> used with the current formatter.</returns>
		public SerializationBinder Binder
		{
			get
			{
				return m_binder;
			}

			set
			{
				m_binder = value;
			}
		}

		/// <summary>
		/// When overridden in a derived class, gets or sets the <see cref="T:System.Runtime.Serialization.StreamingContext"></see> used for the current serialization.
		/// </summary>
		/// <value></value>
		/// <returns>The <see cref="T:System.Runtime.Serialization.StreamingContext"></see> used for the current serialization.</returns>
		public StreamingContext Context
		{
			get
			{
				return m_streamingContext;
			}

			set
			{
				m_streamingContext = value;
			}
		}

		/// <summary>
		/// When overridden in a derived class, gets or sets the <see cref="T:System.Runtime.Serialization.ISurrogateSelector"></see> used with the current formatter.
		/// </summary>
		/// <value></value>
		/// <returns>The <see cref="T:System.Runtime.Serialization.ISurrogateSelector"></see> used with the current formatter.</returns>
		public ISurrogateSelector SurrogateSelector
		{
			get
			{
				return m_surrogateSelector;	
			}

			set
			{
				m_surrogateSelector = value;
			}
		}

		/// <summary>
		/// Gets the XML settings object.
		/// </summary>
		/// <value>The XML settings.</value>
		public XmlWriterSettings XmlSettings
		{
			get
			{
				return m_xmlSettings;
			}
		}

        /// <summary>
        /// 
        /// </summary>
        public IFormatterConverter DeserializationTypeConverter
        {
            get
            {
                return m_deserializationTypeConverter;
            }
            set
            {
                m_deserializationTypeConverter = value;
            }
        }

		/// <summary>
		/// When overridden in a derived class, serializes the graph of objects with the specified root to the stream already attached to the formatter.
		/// </summary>
		/// <param name="serializationStream">The stream to which the objects are serialized.</param>
		/// <param name="graph">The object at the root of the graph to serialize.</param>
		public void Serialize( Stream serializationStream, object graph )
		{
            try
            {
                // Reset the internal classes
                m_typeManager.Reset();
                m_objectManager.Reset( m_streamingContext );

                m_xmlWriter = XmlWriter.Create( serializationStream, m_xmlSettings );
                m_xmlWriter.WriteStartDocument();

                m_xmlWriter.WriteStartElement( "XmlFormatter" );
                m_xmlWriter.WriteAttributeString( "Version", CurrentVersion.ToString() );

                // Write out the Object Graph
                WriteMember( "ObjectGraph", graph );

                // Write out the type Table
                WriteTypeRefTable();

                m_xmlWriter.WriteEndElement();

                m_xmlWriter.WriteEndDocument();
                m_xmlWriter.Flush();
            }
            catch( Exception ex )
            {
                s_logger.Error( "Serialize failed", ex );
                throw;
            }
			finally
			{
				if( m_xmlWriter != null )
				{
					m_xmlWriter.Close();
					m_xmlWriter = null;
				}
			}
		}

		/// <summary>
		/// When overridden in a derived class, deserializes the stream attached to the formatter when it was created, creating a graph of objects identical to the graph originally serialized into that stream.
		/// </summary>
		/// <param name="serializationStream">The stream to deserialize.</param>
		/// <returns>
		/// The top object of the deserialized graph of objects.
		/// </returns>
		public object Deserialize( Stream serializationStream )
		{
			object ret = null;

            try
            {
                m_xmlDoc = new XmlDocument();
                m_xmlDoc.Load( serializationStream );

                XmlNode rootNode = m_xmlDoc.SelectSingleNode( "XmlFormatter" );
                if( rootNode != null )
                {
                    // Get the version of the formatter that serialized the object graph
                    XmlAttribute versionAttribute = rootNode.Attributes[ "Version" ];
                    if( versionAttribute != null )
                    {
                        Version serializedVersion = new Version( versionAttribute.InnerText );
                        if( serializedVersion <= CurrentVersion )
                        {
                            // Reset the object managers and tables
                            m_typeManager.Reset();
                            m_objectManager.Reset( m_streamingContext );

                            XmlNode typeTableNode = rootNode.SelectSingleNode( "TypeReferences" );
                            if( typeTableNode != null )
                            {
                                // Load the type table 
                                ReadTypeRefTable( typeTableNode );

                                // Read the object graph
                                XmlNode objectGraphNode = rootNode.SelectSingleNode( "ObjectGraph" );
                                if( objectGraphNode != null )
                                {
                                    // Read in the Object Graph
                                    ret = ReadMember( objectGraphNode );

                                    // Fixup any outstanding object reference 
                                    m_objectManager.FixupObjectGraph();

                                    // If the root object is null and there was a root object registered 
                                    // then get the object from the object manager.
                                    if( ret == null && m_objectManager.RootObjectId > 0 )
                                    {
                                        ret = m_objectManager.GetObject( m_objectManager.RootObjectId );
                                    }

                                    // If the root object is and ObjectReference the get the real object.
                                    if( ret is IObjectReference )
                                    {
                                        ret = ( (IObjectReference) ret ).GetRealObject( m_streamingContext );
                                    }

                                    // Fire the deserialize completed events
                                    m_objectManager.FireOnDeserializationCompleteEvent();
                                }
                                else
                                {
                                    throw new SerializationException( "ObjectGraph tag is missing." );
                                }
                            }
                            else
                            {
                                throw new SerializationException( "TypeReferences tag is missing." );
                            }
                        }
                        else
                        {
                            throw new SerializationException( string.Format( "The stream was serializied by a newer version of the XmlFormatter class, version {0}. This version {1} can not deserailize the stream.", serializedVersion, CurrentVersion ) );
                        }
                    }
                    else
                    {
                        throw new SerializationException( "XmlFormatter tag is missing version attribute" );
                    }
                }
                else
                {
                    // Reset the stream to the beginning
                    serializationStream.Seek( 0, SeekOrigin.Begin );

                    // Disable the warning that the XmlFormatter class is Obsolete
#pragma warning disable 618

                    // This is a 1.0 graph so use the XmlFormater (ie 1.0) class to deserialize it.
                    XmlFormatter xmlFormater = new XmlFormatter();
                    ret = xmlFormater.Deserialize( serializationStream );
#pragma warning restore 618
                }
            }
			finally
			{
				m_xmlDoc = null;
			}

			return ret;
		}

		/// <summary>
		/// Writes the member.
		/// </summary>
		/// <param name="memberName">Name of the member.</param>
		/// <param name="data">The data.</param>
		protected void WriteMember( string memberName, object data )
		{
			if( data == null )
			{
				WriteObjectRef( data, memberName, typeof( object ) );
			}
			else
			{
				Type type = data.GetType();
				
				// Serialize the basic/standard types
				if( type == typeof( bool ) )
				{
					WriteBoolean( (bool) data, memberName );
				}
				else if( type == typeof( byte ) )
				{
					WriteByte( (byte) data, memberName );
				}
				else if( type == typeof( sbyte ) )
				{
					WriteSByte( (sbyte) data, memberName );
				}
				else if( type == typeof( char ) )
				{
					WriteChar( (char) data, memberName );
				}
				else if( type == typeof( ushort ) )
				{
					WriteUInt16( (ushort) data, memberName );
				}
				else if( type == typeof( short ) )
				{
					WriteInt16( (short) data, memberName );
				}
				else if( type == typeof( uint ) )
				{
					WriteUInt32( (uint) data, memberName );
				}
				else if( type == typeof( int ) )
				{
					WriteInt32( (int) data, memberName );
				}
				else if( type == typeof( ulong ) )
				{
					WriteUInt64( (ulong) data, memberName );
				}
				else if( type == typeof( long ) )
				{
					WriteInt64( (long) data, memberName );
				}
				else if( type == typeof( float ) )
				{
					WriteSingle( (float) data, memberName );
				}
				else if( type == typeof( double ) )
				{
					WriteDouble( (double) data, memberName );
				}
				else if( type == typeof( decimal ) )
				{
					WriteDecimal( (decimal) data, memberName );
				}
				else if( type == typeof( string ) )
				{
					WriteString( (string) data, memberName );
				}

				// Serialize special types or special cases.
				else if( type == typeof( byte[] ) )
				{
					WriteByteArray( (byte[]) data, memberName );
				}
				else if( type == typeof( Type ) || type.FullName == "System.RuntimeType" )
				{
					WriteType( (Type) data, memberName );
				}

				// Serialize arrays and complex objects.
				else if( type.IsArray )
				{
					WriteArray( data, memberName, type );
				}
				else
				{
					WriteObjectRef( data, memberName, type );
				}
			}
		}

		/// <summary>
		/// When overridden in a derived class, writes a Boolean value to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		protected void WriteBoolean( bool value, string name )
		{
			WriteIntegerType<bool>( value, name );
		}

		/// <summary>
		/// When overridden in a derived class, writes an 8-bit unsigned integer to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		protected void WriteByte( byte value, string name )
		{
			WriteIntegerType<byte>( value, name );
		}

		/// <summary>
		/// When overridden in a derived class, writes an 8-bit signed integer to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		protected void WriteSByte( sbyte value, string name )
		{
			WriteIntegerType<sbyte>( value, name );
		}

		/// <summary>
		/// When overridden in a derived class, writes a Unicode character to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		/// <remarks>
		///	The char type is stored as a numeric value and not as characher data.  This allows
		/// for unicode and escape chars sequences to be saved in the XML.
		/// </remarks>
		protected void WriteChar( char value, string name )
		{
			m_xmlWriter.WriteStartElement( XmlConvert.EncodeName( name ) );
			WriteTypeRefAttribute( value.GetType() );

			m_xmlWriter.WriteValue( (ushort) value );

			m_xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// When overridden in a derived class, writes a 16-bit unsigned integer to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		protected void WriteUInt16( ushort value, string name )
		{
			WriteIntegerType<ushort>( value, name );
		}

		/// <summary>
		/// When overridden in a derived class, writes a 16-bit signed integer to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		protected void WriteInt16( short value, string name )
		{
			WriteIntegerType<short>( value, name );
		}

		/// <summary>
		/// When overridden in a derived class, writes a 32-bit unsigned integer to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		protected void WriteUInt32( uint value, string name )
		{
			WriteIntegerType<uint>( value, name );
		}

		/// <summary>
		/// When overridden in a derived class, writes a 32-bit signed integer to the stream.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		protected void WriteInt32( int value, string name )
		{
			WriteIntegerType<int>( value, name );
		}

		/// <summary>
		/// When overridden in a derived class, writes a 64-bit unsigned integer to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		protected void WriteUInt64( ulong value, string name )
		{
			WriteIntegerType<ulong>( value, name );
		}

		/// <summary>
		/// When overridden in a derived class, writes a 64-bit signed integer to the stream.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		protected void WriteInt64( long value, string name )
		{
			WriteIntegerType<long>( value, name );
		}

		/// <summary>
		/// When overridden in a derived class, writes a single-precision floating-point number to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		protected void WriteSingle( float value, string name )
		{
			WriteNonIntegerType<float>( value, BitConverter.GetBytes( value ), name );
		}

		/// <summary>
		/// When overridden in a derived class, writes a double-precision floating-point number to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		protected void WriteDouble( double value, string name )
		{
			WriteNonIntegerType<double>( value, BitConverter.GetBytes( value ), name );
		}

		/// <summary>
		/// When overridden in a derived class, writes a <see cref="T:System.Decimal"></see> value to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The value to write.</param>
		/// <param name="name">The name of the member.</param>
		protected void WriteDecimal( decimal value, string name )
		{
			WriteIntegerType<decimal>( value, name );
		}

		/// <summary>
		/// Writes a <see cref="T:System.String"></see> value to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="name">The name.</param>
		protected virtual void WriteString( string value, string name )
		{
			m_xmlWriter.WriteStartElement( XmlConvert.EncodeName( name ) );
			WriteTypeRefAttribute( value.GetType() );

			m_xmlWriter.WriteValue( value );

			m_xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// Writes a <see cref="T:System.Byte"></see> array value to the stream already attached to the formatter.
		/// </summary>
		/// <param name="bytes">The bytes.</param>
		/// <param name="name">The name.</param>
		protected void WriteByteArray( byte[] bytes, string name )
		{
			m_xmlWriter.WriteStartElement( XmlConvert.EncodeName( name ) );
			WriteTypeRefAttribute( typeof( byte[] ) );
			m_xmlWriter.WriteAttributeString( "Base64String", "true" );

			m_xmlWriter.WriteValue( Convert.ToBase64String( bytes ) );

			m_xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// Writes the type.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="name">The name.</param>
		protected virtual void WriteType( Type value, string name )
		{
			m_xmlWriter.WriteStartElement( XmlConvert.EncodeName( name ) );
			WriteTypeRefAttribute( typeof( Type ) );

			m_xmlWriter.WriteValue( value.AssemblyQualifiedName );

			m_xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// When overridden in a derived class, writes an array to the stream already attached to the formatter.
		/// </summary>
		/// <param name="obj">The array to write.</param>
		/// <param name="name">The name of the array.</param>
		/// <param name="memberType">The type of elements that the array holds.</param>
		protected void WriteArray( object obj, string name, Type memberType )
		{
			m_xmlWriter.WriteStartElement( XmlConvert.EncodeName( name ) );
			WriteTypeRefAttribute( obj.GetType() );

			string elementName = name + "_element";

			// Write the elements of the array
			Array array = (Array) obj;
			foreach( object element in array )
			{
				WriteMember( elementName, element );
			}

			m_xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// When overridden in a derived class, writes an object reference to the stream already attached to the formatter.
		/// </summary>
		/// <param name="value">The object reference to write.</param>
		/// <param name="name">The name of the member.</param>
		/// <param name="memberType">The type of object the reference points to.</param>
		protected virtual void WriteObjectRef( object value, string name, Type memberType )
		{
			m_xmlWriter.WriteStartElement( XmlConvert.EncodeName( name ) );
			WriteTypeRefAttribute( memberType );

			if( value != null )
			{
				// Get the id of the object
				bool firstTime = false;
				long objectId = m_objectManager.GetObjectId( value, out firstTime );

				// If this is the first time we have seen the object then serailize it.
				if( firstTime )
				{
					m_objectManager.FireOnSerializingEvent( value );

					m_xmlWriter.WriteAttributeString( "ObjectId", objectId.ToString() );

					// Check to see if the type has a surrogate to serialize it
					if( !WriteObjectBySurrogate( value, memberType ) )
					{
						// Make sure the object is serializable
						if( memberType.IsSerializable )
						{
							// Check to see if the object uses custom serialization
							if( !WriteObjectByISerializable( value, memberType ) )
							{
								// Deserialize the object using reflection.
								MemberInfo[] members = FormatterServices.GetSerializableMembers( memberType );
								object[] membersData = FormatterServices.GetObjectData( value, members );

								int count = members.Length;
								for( int x = 0; x < count; x++ )
								{
									WriteMember( members[ x ].Name, membersData[ x ] );
								}
							}
						}
						else
						{
							throw new SerializationException( string.Format( "Object type {0} is not marked as Serializable.", memberType.FullName ) );
						}
					}

					m_objectManager.FireOnSerializedEvent( value );
				}
				else
				{
					// We have seen this object before so we just need to write out a ObjectRef attribute
					m_xmlWriter.WriteAttributeString( "ObjectRef", objectId.ToString() );
				}
			}
			else
			{
				m_xmlWriter.WriteValue( "null" );
			}

			m_xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// Writes the object out using a surrogate.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="memberType">Type of the member.</param>
		/// <returns></returns>
		private bool WriteObjectBySurrogate( object value, Type memberType )
		{
			ISurrogateSelector selector = null;
			ISerializationSurrogate surrogate = null;

			// If we have a selector then see if this type has a surrogate
			if( m_surrogateSelector != null )
			{
				surrogate = m_surrogateSelector.GetSurrogate( memberType, m_streamingContext, out selector );

				// If we have a surrogate then use it to deserialize the object, even if the
				// object is marked serializeable or supports one of the serializable interfaces
				if( surrogate != null )
				{
					FormatterConverter formatterConverter = new FormatterConverter();
					SerializationInfo serializationInfo = new SerializationInfo( memberType, formatterConverter );

					// Have the surrogate load the object data into the serializationInfo object.
					surrogate.GetObjectData( value, serializationInfo, m_streamingContext );

					// Write out the the type from the serialization info object incase this is 
					// a proxy object and we have to use a differen tobject for deserailization.
					WriteSerializationTypeRef( memberType, serializationInfo );

					// Write attributes with the type information for the selector and the surrogate.
					WriteTypeRefAttribute( surrogate.GetType(), "SerializationSurrogateTypeRef" );
					WriteTypeRefAttribute( selector.GetType(), "SurrogateSelectorTypeRef" );

					WriteSerializationInfo( serializationInfo );

					return true;
				}
			}

			return false;
		}

		/// <summary>
		/// Writes the object using the ISerializable.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="memberType">Type of the member.</param>
		/// <returns></returns>
		private bool WriteObjectByISerializable( object value, Type memberType )
		{
			if( value is ISerializable )
			{
				FormatterConverter formatterConverter = new FormatterConverter();
				SerializationInfo serializationInfo = new SerializationInfo( memberType, formatterConverter );

				// Have the object load it's data into the serializationInfo object.
				( (ISerializable) value ).GetObjectData( serializationInfo, m_streamingContext );

				// Write out the the type from the serialization info object incase this is 
				// a proxy object and we have to use a differen tobject for deserailization.
				WriteSerializationTypeRef( memberType, serializationInfo );

				// Write attributes with the value of ISerializable as true.
				m_xmlWriter.WriteAttributeString( "ISerializable", "true" );

				WriteSerializationInfo( serializationInfo );

				return true;
			}

			return false;
		}


		/// <summary>
		/// Writes the the numeric types
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="name">The name.</param>
		private void WriteIntegerType<T>( T value, string name )
		{
			m_xmlWriter.WriteStartElement( XmlConvert.EncodeName( name ) );
			WriteTypeRefAttribute( value.GetType() );

			m_xmlWriter.WriteValue( value.ToString() );

			m_xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// Writes the type of the non-integer.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="bits">The bits.</param>
		/// <param name="name">The name.</param>
		/// <remarks>
		/// In order to matain the presition of the values we convert them to a byte array
		/// and then save the array of bytes.  If you .ToString() the value they are rounded
		/// off and you will lose presition.
		/// </remarks>
		private void WriteNonIntegerType<T>( T value, byte[] bits, string name )
		{
			m_xmlWriter.WriteStartElement( XmlConvert.EncodeName( name ) );
			WriteTypeRefAttribute( value.GetType() );
			m_xmlWriter.WriteAttributeString( "StringValue", value.ToString() );

			// Write out the value as an array of bytes
			WriteArray( bits, "Bytes", typeof( byte ) );

			m_xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// Gets the type of the serialization info.
		/// </summary>
		/// <param name="memberType">Type of the member.</param>
		/// <param name="serializationInfo">The serialization info.</param>
		private void WriteSerializationTypeRef( Type memberType, SerializationInfo serializationInfo )
		{
			Type serializationType = null;

			if( m_binder != null )
			{
				serializationType = m_binder.BindToType( serializationInfo.AssemblyName, serializationInfo.FullTypeName );
			}
			else
			{
				// No binder
				TypeName typeName = new TypeName( serializationInfo.AssemblyName, serializationInfo.FullTypeName );
				serializationType = Type.GetType( typeName.FullyQualifiedAssemblyName );
			}

			// Only write out the attribuate if the types are different.
			if( serializationType != memberType )
			{
				WriteTypeRefAttribute( serializationType, "SerializationTypeRef" );
			}
		}



		/// <summary>
		/// Writes the serialization info.
		/// </summary>
		/// <param name="serializationInfo">The serialization info.</param>
		private void WriteSerializationInfo( SerializationInfo serializationInfo )
		{
			// Write out the elements of the SerializationInfo.
			foreach( SerializationEntry entry in serializationInfo )
			{
				WriteMember( entry.Name, entry.Value );
			}
		}

		/// <summary>
		/// Writes the type ref attribute.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <returns></returns>
		private long WriteTypeRefAttribute( Type type )
		{
			return WriteTypeRefAttribute( type, "TypeRef" );
		}

		/// <summary>
		/// Writes the type ref attribute.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <param name="attribName">Name of the attrib.</param>
		/// <returns></returns>
		private long WriteTypeRefAttribute( Type type, string attribName )
		{
			long typeId = m_typeManager.GetTypeId( type );

			// Write out an attribute with the type id as a reference.
			m_xmlWriter.WriteAttributeString( attribName, typeId.ToString() );

			return typeId;
		}

		/// <summary>
		/// Writes the type ref table.
		/// </summary>
		private void WriteTypeRefTable()
		{
			m_xmlWriter.WriteStartElement( "TypeReferences" );

			foreach( KeyValuePair<long, Type> entry in m_typeManager )
			{
				m_xmlWriter.WriteStartElement( "Type" );
				m_xmlWriter.WriteAttributeString( "TypeRef", entry.Key.ToString() );
				m_xmlWriter.WriteValue( entry.Value.AssemblyQualifiedName );
				m_xmlWriter.WriteEndElement();
			}

			m_xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// Reads the member.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <returns></returns>
		private object ReadMember( XmlNode node )
		{
			XmlAttribute typeRefAttrib = node.Attributes[ "TypeRef" ];
			if( typeRefAttrib != null )
			{
				// Lookup the type in the table
				Type memberType = m_typeManager.GetType( Convert.ToInt64( typeRefAttrib.Value ) );
				if( memberType != null )
				{
					return ReadMember( node, memberType );
				}
				else
				{
					throw new SerializationException( string.Format( "TypeRef value on node {0} does not exist in the TypeRefence table", node.Name ) );
				}
			}
			else
			{
				throw new SerializationException( string.Format( "Missing 'TypeRef' attribute on node {0}", node.Name ) );
			}
		}

		/// <summary>
		/// Reads the member.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="memberType">Type of the member.</param>
		/// <returns></returns>
		private object ReadMember( XmlNode node, Type memberType )
		{
			object ret = null;

			// Deserialize the basic/standard types
			if( memberType == typeof( bool ) )
			{
				ret = Convert.ToBoolean( node.InnerText );
			}
			else if( memberType == typeof( byte ) )
			{
				ret = Convert.ToByte( node.InnerText );
			}
			else if( memberType == typeof( sbyte ) )
			{
				ret = Convert.ToSByte( node.InnerText );
			}
			else if( memberType == typeof( char ) )
			{
				ret = (char) Convert.ToInt16( node.InnerText );
			}
			else if( memberType == typeof( ushort ) )
			{
				ret = Convert.ToUInt16( node.InnerText );
			}
			else if( memberType == typeof( short ) )
			{
				ret = Convert.ToInt16( node.InnerText );
			}
			else if( memberType == typeof( uint ) )
			{
				ret = Convert.ToUInt32( node.InnerText );
			}
			else if( memberType == typeof( int ) )
			{
				ret = Convert.ToInt32( node.InnerText );
			}
			else if( memberType == typeof( ulong ) )
			{
				ret = Convert.ToUInt64( node.InnerText );
			}
			else if( memberType == typeof( long ) )
			{
				ret = Convert.ToInt64( node.InnerText );
			}
			else if( memberType == typeof( float ) )
			{
				byte[] bytes = (byte[]) ReadArray( node.ChildNodes[ 0 ], typeof( byte[] ) );
				ret = BitConverter.ToSingle( (byte[]) bytes, 0 );
			}
			else if( memberType == typeof( double ) )
			{
				byte[] bytes = (byte[]) ReadArray( node.ChildNodes[ 0 ], typeof( byte[] ) );
				ret = BitConverter.ToDouble( (byte[]) bytes, 0 );
			}
			else if( memberType == typeof( decimal ) )
			{
				ret = Convert.ToDecimal( node.InnerText );
			}
			else if( memberType == typeof( string ) )
			{
				ret = node.InnerText;
			}

			// Deserialize special types or special cases.
			else if( memberType == typeof( byte[] ) )
			{
				ret = ReadByteArray( node, memberType );
			}
			else if( memberType == typeof( Type ) || memberType.FullName == "System.RuntimeType" )
			{
				ret = ReadType( node, memberType );
			}

			// Deserialize arrays and complex objects.
			else if( memberType.IsArray )
			{
				ret = ReadArray( node, memberType );
			}
			else
			{
				ret = ReadObject( node, memberType );
			}

			return ret;
		}

		/// <summary>
		/// Reads the byte array.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="memberType">Type of the member.</param>
		/// <returns></returns>
		private object ReadByteArray( XmlNode node, Type memberType )
		{
			// This attribute was added to impove on performance.  If we 
			// write the byte[] as an array then each byte (element) gets
			// it's own node.  So if the Base64String attribute is missing
			// is false then process the byte[] as an array.
			XmlAttribute attrib = node.Attributes[ "Base64String" ];
			if( attrib != null && Convert.ToBoolean( attrib.InnerText ) )
			{
				return Convert.FromBase64String( node.InnerText );
			}

			return ReadArray( node, memberType );
		}

		/// <summary>
		/// Reads the type.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="memberType">Type of the member.</param>
		/// <returns></returns>
		private object ReadType( XmlNode node, Type memberType )
		{
			// If older code before this fix.
			XmlNode dataNode = node.SelectSingleNode( "./Data" );
			if( dataNode != null )
			{
				XmlNode assemblyNode = node.SelectSingleNode( "./AssemblyName" );
				return m_binder.BindToType( assemblyNode.InnerText, dataNode.InnerText );
			}
			else
			{
				TypeName typeName = new TypeName( node.InnerText );
				return m_binder.BindToType( typeName.AssemblyName, typeName.FullTypeName );
			}
		}

		/// <summary>
		/// Reads the array.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="memberType">Type of the member.</param>
		/// <returns></returns>
		private object ReadArray( XmlNode node, Type memberType )
		{
			int count = node.ChildNodes.Count;

			// Create the array object
			Array array = Array.CreateInstance( memberType.GetElementType(), count );

			// Read each the element of the array
			for( int x = 0; x < count; x++ )
			{
				object ret = ReadMember( node.ChildNodes[ x ] );
				if( ret is IObjectReference )
				{
					long objectId = m_objectManager.GetObjectId( ret );
					m_objectManager.RegisterObjectReferenceFixup( objectId, array, x );
				}
				else
				{
					array.SetValue( ret, x );
				}
			}

			return array;
		}

		/// <summary>
		/// Reads the object.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="memberType">Type of the member.</param>
		/// <returns></returns>
		private object ReadObject( XmlNode node, Type memberType )
		{
			object ret = null;

			// Is the object null or not
			if( node.InnerXml.ToLower() != "null" )
			{
				bool isReference = false;
				
				long objectId = GetObjectId( node, out isReference );
				if( !isReference )
				{
					memberType = GetDeserializableType( node, memberType );

					ret = ReadObjectBySurrogate( objectId, node, memberType );
					if( ret == null )
					{
						if( memberType.IsSerializable )
						{
							ret = ReadObjectByISerializable( objectId, node, memberType );
							if( ret == null )
							{
								ret = ReadObjectBySerializableAttribute( objectId, node, memberType );
							}
						}
						else
						{
							throw new SerializationException( string.Format( "Object type {0} is not marked as Serializable.", memberType.FullName ) );
						}
					}
				}
				else
				{
					// Get the object that we have already deserialized
					ret = m_objectManager.GetObject( objectId );
					if( ret == null )
					{
						throw new NotImplementedException( "A forward instance of an object was found.  This is currently not supported in the Xml Formatter." );
					}
				}
			}

			return ret;
		}

		/// <summary>
		/// Reads the object by surrogate.
		/// </summary>
		/// <param name="objectId">The object id.</param>
		/// <param name="node">The node.</param>
		/// <param name="memberType">Type of the member.</param>
		/// <returns></returns>
		private object ReadObjectBySurrogate( long objectId, XmlNode node, Type memberType )
		{
			object ret = null;

			// See if the node used a surrogate to serialize the object
			XmlAttribute surrogateTypeRefAttrib = node.Attributes[ "SerializationSurrogateTypeRef" ];
			if( surrogateTypeRefAttrib != null )
			{
				Type surrogateType = m_typeManager.GetType( Convert.ToInt64( surrogateTypeRefAttrib.Value ) );
				if( surrogateType != null )
				{
					ISerializationSurrogate surrogate = LoadSerializationSurrogate( memberType, surrogateType );
					if( surrogate != null )
					{
						SerializationInfo serializationInfo = ReadSerializationInfo( node, memberType );

						// Create an object and load it from the surrogate
						ret = FormatterServices.GetUninitializedObject( memberType );
						m_objectManager.RegisterObject( ret, objectId );
						m_objectManager.FireOnDeserializingEvent( ret );

						object newObj = surrogate.SetObjectData( ret, serializationInfo, m_streamingContext, m_surrogateSelector );
						
						// Replace the object if we got a new one
						if( newObj != null )
						{
							ret = newObj;
							m_objectManager.ReplaceObject( newObj, objectId );
						}

						m_objectManager.FireOnDeserializedEvent( ret );
					}
					else
					{
						//throw new SerializationException( string.Format( "Unable to find the Serialization Surrogate object for node {0}", node.Name ) );
					}
				}
				else
				{
					throw new SerializationException( string.Format( "SurrogateSelectorTypeRef value on node {0} does not exist in the TypeRefence table", node.Name ) );
				}
			}

			return ret;
		}

		/// <summary>
		/// Reads the object I serializable.
		/// </summary>
		/// <param name="objectId">The object id.</param>
		/// <param name="node">The node.</param>
		/// <param name="memberType">Type of the member.</param>
		/// <returns></returns>
		private object ReadObjectByISerializable( long objectId, XmlNode node, Type memberType )
		{
			object ret = null;

			// We must check both that the memberType implements the ISerializable interface and that the data 
			// was written through the ISerializable interface.  In some cases the memberType may not support the 
			// ISeraliable interface in wich case a proxy object is used to deserialize the object.
			if( memberType.FindInterfaces( new TypeFilter( InterfaceFilter ), typeof( ISerializable ) ).Length > 0 )
			{
				// Was the object serialized with the ISerializable interface
				XmlAttribute serializableAttrib = node.Attributes[ "ISerializable" ];
				if( serializableAttrib != null && serializableAttrib.Value.ToLower() == "true" )
				{
					// Find the serialzation constructor
					ConstructorInfo construct = memberType.GetConstructor( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
						null, new Type[] { typeof( SerializationInfo ), typeof( StreamingContext ) }, null );
					if( construct != null )
					{
						SerializationInfo serializationInfo = ReadSerializationInfo( node, memberType );
						ret = FormatterServices.GetUninitializedObject( memberType );
						m_objectManager.RegisterObject( ret, objectId );
						m_objectManager.FireOnDeserializingEvent( ret );

						construct.Invoke( ret, new object[] { serializationInfo, m_streamingContext } );

						m_objectManager.FireOnDeserializedEvent( ret );
					}
					else
					{
						throw new SerializationException( string.Format( "Unable to find the ISerializable constructor for type {0}", memberType.AssemblyQualifiedName ) );
					}
				}
			}

			return ret;
		}

		/// <summary>
		/// Reads the object by serializable attribute.
		/// </summary>
		/// <param name="objectId">The object id.</param>
		/// <param name="node">The node.</param>
		/// <param name="memberType">Type of the member.</param>
		/// <returns></returns>
		private object ReadObjectBySerializableAttribute( long objectId, XmlNode node, Type memberType )
		{
			object ret = FormatterServices.GetUninitializedObject( memberType );
			m_objectManager.RegisterObject( ret, objectId );
			m_objectManager.FireOnDeserializingEvent( ret );

			// Load all the child objects into the table.
			Dictionary<string, object> objectTable = new Dictionary<string, object>();
			foreach( XmlNode childNode in node.ChildNodes )
			{
				object childObject = ReadMember( childNode );
				objectTable.Add( childNode.Name, childObject );
			}

			// Get the members of the object and walk the members and line up the 
			// data memebers based on thier name.
			MemberInfo[] members = FormatterServices.GetSerializableMembers( memberType );
			
			int count = members.Length;
			object[] data = new object[ count ];
			for( int x = 0; x < count; x++ )
			{
				MemberInfo memberInfo = members[ x ];
				string objectName = XmlConvert.EncodeName( memberInfo.Name );
				if( objectTable.ContainsKey( objectName ) )
				{
					object childObject = objectTable[ objectName ];
					if( childObject != null )
					{
						if( childObject is IObjectReference )
						{
							// Register the object for a fixup
							long childObjectId = m_objectManager.GetObjectId( childObject );
							m_objectManager.RegisterObjectReferenceFixup( childObjectId, objectId, memberInfo );
							data[ x ] = null;
						}
						else
						{
							// Assign the object to it's pranet
							data[ x ] = childObject;
						}
					}
				}
			}

            // Attempt to convert types in data to the types needed by the object
            ConvertDataTypes(ret, members, data);

			ret = FormatterServices.PopulateObjectMembers(ret, members, data );
			m_objectManager.FireOnDeserializedEvent( ret );

			return ret;
		}

        /// <summary>
        /// Attempts to convert each property or field in the data array to the type expected in the target object
        /// </summary>
        /// <param name="target"></param>
        /// <param name="members"></param>
        /// <param name="data"></param>
        private void ConvertDataTypes(object target, MemberInfo[] members, object[] data)
        {
            Type targetType = target.GetType(); // The type of the object we are deserializing to
            for (int i = 0; i < members.Length; i++)
            {
                MemberInfo info = members[i];
                // Only do value types for now... this can be removed if non-value types need to be converted
                if (info != null && data[i] != null && data[i] is ValueType)
                {
                    Type convertToType = null;

                    // Get the type to convert to (from either a field or property)
                    if (info.MemberType == MemberTypes.Field)
                    {
                        FieldInfo fieldInfo = targetType.GetField(info.Name);
                        if (fieldInfo != null)
                        {
                            convertToType = fieldInfo.FieldType;
                        }
                    }
                    else if (info.MemberType == MemberTypes.Property)
                    {
                        PropertyInfo propertyInfo = targetType.GetProperty(info.Name);
                        if (propertyInfo != null)
                        {
                            convertToType = propertyInfo.PropertyType;
                        }
                    }

                    if (convertToType != null)
                    {
                        if (data[i].GetType() != convertToType)
                        {
                            try
                            {
                                // If we were given a FormatterConverter use it, otherwise use the System.Convert method
                                if (DeserializationTypeConverter != null)
                                {
                                    data[i] = DeserializationTypeConverter.Convert(data[i], convertToType);
                                }
                                else
                                {
                                    data[i] = Convert.ChangeType(data[i], convertToType);
                                }
                            }
                            catch
                            {
                                // unable to cast to this type...  we'll blow up downstream
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Interfaces the filter.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="criteria">The criteria.</param>
        /// <returns></returns>
        public bool InterfaceFilter(Type type, object criteria)
		{
			return type.Equals( criteria );
		}

		/// <summary>
		/// Reads the serialization info.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="memberType">Type of the member.</param>
		/// <returns></returns>
		private SerializationInfo ReadSerializationInfo( XmlNode node, Type memberType )
		{
			SerializationInfo serializationInfo = new SerializationInfo( memberType, new FormatterConverter() );

			// Load the serializationInfo object with the child elements of the current node
			foreach( XmlNode childNode in node.ChildNodes )
			{
				object childObj = ReadMember( childNode );
				serializationInfo.AddValue( XmlConvert.DecodeName( childNode.Name ), childObj );
			}

			return serializationInfo;
		}

		/// <summary>
		/// Reads the type table.
		/// </summary>
		/// <param name="tableNode">The table node.</param>
		private void ReadTypeRefTable( XmlNode tableNode )
		{
			foreach( XmlNode child in tableNode.ChildNodes )
			{
				if( child.Name == "Type" )
				{
					XmlAttribute typeRefAttrib = child.Attributes[ "TypeRef" ];
					if( typeRefAttrib != null )
					{
						long typeId = Convert.ToInt64( typeRefAttrib.Value );
						string qualifiedAssemblyName = child.InnerText;

						Type type = null;
						if( m_binder != null )
						{
							TypeName theTypeName = new TypeName( qualifiedAssemblyName );
							type = m_binder.BindToType( theTypeName.FullyQualifiedAssemblyName, theTypeName.FullTypeName );
						}
						else
						{
							// No binder so just match the fully qualifed type name
							type = Type.GetType( qualifiedAssemblyName );
						}

						// If we found a type then add it to the type manager
						if( type != null )
						{
							m_typeManager.RegisterType( type, typeId );
						}
						else
						{
							throw new XmlException( string.Format( "Unknown Type {0} in the type references table.", qualifiedAssemblyName ) );
						}
					}
					else
					{
						throw new SerializationException( "TypeRef attribute missing from <Type> tag" );
					}
				}
			}
		}

		/// <summary>
		/// Loads the serialization surrogate.
		/// </summary>
		/// <param name="memberType">Type of the member.</param>
		/// <param name="surrogateType">Type of the surrogate.</param>
		/// <returns></returns>
		private ISerializationSurrogate LoadSerializationSurrogate( Type memberType, Type surrogateType )
		{
			ISurrogateSelector selector = null;
			ISerializationSurrogate surrogate = null;

			// If we have a selector then look up the surrogate in it first
			if( m_surrogateSelector != null )
			{
				surrogate = m_surrogateSelector.GetSurrogate( memberType, m_streamingContext, out selector );
			}

			return surrogate;
		}

		/// <summary>
		/// Gets the object id.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="isReference">if set to <c>true</c> [is reference].</param>
		/// <returns></returns>
		private long GetObjectId( XmlNode node, out bool isReference )
		{
			long objectId = -1;
			isReference = false;

			XmlAttribute attrib = node.Attributes[ "ObjectId" ];
			if( attrib != null )
			{
				objectId = Convert.ToInt64( attrib.Value );
			}
			else
			{
				attrib = node.Attributes[ "ObjectRef" ];
				if( attrib != null )
				{
					objectId = Convert.ToInt64( attrib.Value );
					isReference = true;
				}
			}

			return objectId;
		}

		/// <summary>
		/// Gets the type of the deserializable.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <param name="memberType">Type of the member.</param>
		/// <returns></returns>
		private Type GetDeserializableType( XmlNode node, Type memberType )
		{
			XmlAttribute attrib = node.Attributes[ "SerializationTypeRef" ];
			if( attrib != null )
			{
				// Lookup the type in the table
				Type type = m_typeManager.GetType( Convert.ToInt64( attrib.Value ) );
				if( type != null )
				{
					memberType = type;
				}
				else
				{
					throw new SerializationException( string.Format( "SerializationTypeRef value on node {0} does not exist in the TypeRefence table", node.Name ) );
				}
			}

			return memberType;
		}
	}
}
